<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_23</span></h2>

  <p>ndowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>pdemo.setSize(500, 500);</p>

  <p>pdemo.setVisible(true);</p>

  <p>}</p>

  <p>}</p>

  <p>class Plot extends Canvas {</p>

  <p>public int rings = 3;</p>

  <p>}</p>

  <p>class Plot1 extends Plot {</p>

  <p>// Default print() calls paint():</p>

  <p>public void paint(Graphics g) {</p>

  <p>int w = getSize().width;</p>

  <p>int h = getSize().height;</p>

  <p>int xc = w / 2;</p>

  <p>int yc = w / 2;</p>

  <p>int x = 0, y = 0;</p>

  <p>for(int i = 0; i &lt; rings; i++) {</p>

  <p>if(x &lt; xc &amp;&amp; y &lt; yc) {</p>

  <p>g.drawOval(x, y, w, h);</p>

  <p>x += 10; y += 10;</p>

  <p>w -= 20; h -= 20;</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class Plot2 extends Plot {</p>

  <p>// To fit the picture to the page, you must</p>

  <p>// know whether you're printing or painting:</p>

  <p>public void paint(Graphics g) {</p>

  <p>int w, h;</p>

  <p>if(g instanceof PrintGraphics) {</p>

  <p>PrintJob pj =</p>

  <p>((PrintGraphics)g).getPrintJob();</p>

  <p>w = pj.getPageDimension().width;</p>

  <p>h = pj.getPageDimension().height;</p>

  <p>}</p>

  <p>else {</p>

  <p>w = getSize().width;</p>

  <p>h = getSize().height;</p>

  <p>}</p>

  <p>int xc = w / 2;</p>

  <p>int yc = w / 2;</p>

  <p>int x = 0, y = 0;</p>

  <p>for(int i = 0; i &lt; rings; i++) {</p>

  <p>if(x &lt; xc &amp;&amp; y &lt; yc) {</p>

  <p>g.drawOval(x, y, w, h);</p>

  <p>x += 10; y += 10;</p>

  <p>w -= 20; h -= 20;</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class Plot3 extends Plot {</p>

  <p>// Somewhat better. Separate</p>

  <p>// printing from painting:</p>

  <p>public void print(Graphics g) {</p>

  <p>// Assume it's a PrintGraphics object:</p>

  <p>PrintJob pj =</p>

  <p>((PrintGraphics)g).getPrintJob();</p>

  <p>int w = pj.getPageDimension().width;</p>

  <p>int h = pj.getPageDimension().height;</p>

  <p>doGraphics(g, w, h);</p>

  <p>}</p>

  <p>public void paint(Graphics g) {</p>

  <p>int w = getSize().width;</p>

  <p>int h = getSize().height;</p>

  <p>doGraphics(g, w, h);</p>

  <p>}</p>

  <p>private void doGraphics(</p>

  <p>Graphics g, int w, int h) {</p>

  <p>int xc = w / 2;</p>

  <p>int yc = w / 2;</p>

  <p>int x = 0, y = 0;</p>

  <p>for(int i = 0; i &lt; rings; i++) {</p>

  <p>if(x &lt; xc &amp;&amp; y &lt; yc) {</p>

  <p>g.drawOval(x, y, w, h);</p>

  <p>x += 10; y += 10;</p>

  <p>w -= 20; h -= 20;</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这个程序允许我们从一个选择列表框中选择字体（并且我们会注意到很多有用的字体在Java 1.1版中一直受到严格的限制，我们没有任何可以利用的优秀字体安装在我们的机器上）。它使用这些字体去打出粗体，斜体和不同大小的文字。另外，一个新型组件调用过的绘图被创建，以用来示范图形。当打印图形时，绘图拥有的ring将显示在屏幕上和打印在纸上，并且这三个衍生类Plot1，Plot2，Plot3用不同的方法去完成任务以便我们可以看到我们选择的事物。同样，我们也能在一个绘图中改变一些ring――这很有趣，因为它证明了Java 1.1版中打印的脆弱。在我的系统里，当ring计数显示“too high”（究竟这是什么意思？）时，打印机给出错误信息并且不能正确地工作，而当计数给出“low enough”信息时，打印机又能工作得很好。我们也会注意到，当打印到看起来实际大小不相符的纸时页面的大小便产生了。这些特点可能被装入到将来发行的Java中，我们可以使用这个程序来测试它。</p>

  <p>这个程序为促进重复使用，不论何时都可以封装功能到内部类中。例如，不论何时我想开始打印工作（不论图形或文字），我必须创建一个PrintJob打印工作对象，该对象拥有它自己的连同页面宽度和高度的图形对象。创建的PrintJob打印工作对象和提取的页面尺寸一起被封装进PrintData class打印类中。</p>

  <p>1. 打印文字</p>

  <p>打印文字的概念简单明了：我们选择一种字体和大小，决定字符串在页面上存在的位置，并且使用Graphics.drawSrting()方法在页面上画出字符串就行了。这意味着，不管怎样我们必须精确地计算每行字符串在页面上存在的位置并确定字符串不会超出页面底部或者同其它行冲突。如果我们想进行字处理，我们将进行的工作与我们很相配。ChangeFont封装进少量从一种字体到其它的字体的变更方法并自动地创建一个新字体对象和我们想要的字体，款式（粗体和斜体――目前还不支持下划线、空心等）以及点阵大小。它同样会简单地计算字符串的宽度和高度。当我们按下“Print text”按钮时，TBL接收器被激活。我们可以注意到它通过反复创建ChangeFont对象和调用drawString()来在计算出的位置打印出字符串。注意是否这些计算产生预期的结果。（我使用的版本没有出错。）</p>

  <p>2. 打印图形</p>

  <p>按下“Print graphics”按钮时，GBL接收器会被激活。我们需要打印时，创建的PrintData对象初始化，然后我们简单地为这个组件调用print()打印方法。为强制打印，我们必须为图形对象调用dispose()处理方法，并且为PrintData对象调用end()结束方法（或改变为为PrintJob调用end()结束方法。）</p>

  <p>这种工作在绘图对象中继续。我们可以看到基础类绘图是很简单的――它扩展画布并且包括一个中断调用ring来指明多少个集中的ring需要画在这个特殊的画布上。这三个衍生类展示了可达到一个目的的不同的方法：画在屏幕上和打印的页面上。</p>

  <p>Plot1采用最简单的编程方法：忽略绘画和打印的不同，并且过载paint()绘画方法。使用这种工作方法的原因是默认的print()打印方法简单地改变工作方法转而调用Paint()。但是，我们会注意到输出的尺寸依赖于屏幕上画布的大小，因为宽度和高度都是在调用Canvas.getSize()方法时决定是，所以这是合理的。如果我们图像的尺寸一值都是固定不变的，其它的情况都可接受。当画出的外观的大小如此的重要时，我们必须深入了解的尺寸大小的重要性。不凑巧的是，就像我们将在Plot2中看到的一样，这种方法变得很棘手。因为一些我们不知道的好的理由，我们不能简单地要求图形对象以它自己的大小画出外观。这将使整个的处理工作变得十分的优良。相反，如果我们打印而不是绘画，我们必须利用RTTI instanceof关键字（在本书11章中有相应描述）来测试PrintGrapics，然后下溯造型并调用这独特的PrintGraphics方法：getPrintJob()方法。现在我们拥有PrintJob的句柄并且我们可以发现纸张的高度和宽度。这是一种hacky的方法，但也许这对它来说是合理的理由。（在其它方面，到如今我们看到一些其它的库设计，因此，我们可能会得到设计者们的想法。）</p>

  <p>我们可以注意到Plot2中的paint()绘画方法对打印和绘图的可能性进行审查。但是因为当打印时Print()方法将被调用，那么为什么不使用那种方法呢？这种方法同样也在Plot3中也被使用，并且它消除了对instanceof使用的需求，因为在Print()方法中我们可以假设我们能对一个PrintGraphics对象造型。这样也不坏。这种情况被放置公共绘画代码到一个分离的doGraphics()方法的办法所改进。</p>

  <p>2. 在程序片内运行帧</p>

  <p>如果我们想在一个程序片中打印会怎以样呢？很好，为了打印任何事物我们必须通过工具组件对象的getPrintJob()方法拥有一个PrintJob对象，设置唯一的一个帧对象而不是一个程序片对象。于是它似乎可能从一个应用程序中打印，而不是从一个程序片中打印。但是，它变为我们可以从一个程序片中创建一个帧（相反的到目前为止，我在程序片或应用程序例子中所做的，都可以生成程序片并安放帧。）。这是一个很有用的技术，因为它允许我们在程序片中使用一些应用程序（只要它们不妨碍程序片的安全）。但是，当应用程序窗口在程序片中出现时，我们会注意到WEB浏览器插入一些警告在它上面，其中一些产生“Warning:Applet Window.（警告：程序片窗口）”的字样。</p>

  <p>我们会看到这种技术十分直接的安放一个帧到程序片中。唯一的事是当用户关闭它时我们必须增加帧的代码（代替调用System.exit()）：</p>

  <p>//: PrintDemoApplet.java</p>

  <p>// Creating a Frame from within an Applet</p>

  <p>import java.applet.*;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>public class PrintDemoApplet extends Applet {</p>

  <p>public void init() {</p>

  <p>Button b = new Button("Run PrintDemo");</p>

  <p>b.addActionListener(new PDL());</p>

  <p>add(b);</p>

  <p>}</p>

  <p>class PDL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>final PrintDemo pd = new PrintDemo();</p>

  <p>pd.addWindowListener(new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e){</p>

  <p>pd.dispose();</p>

  <p>}</p>

  <p>});</p>

  <p>pd.setSize(500, 500);</p>

  <p>pd.show();</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>伴随Java 1.1版的打印支持功能而来的是一些混乱。一些宣传似乎声明我们能在一个程序片中打印。但Java的安全系统包含了一个特点，可停止一个正在初始化打印工作的程序片，初始化程序片需要通过一个Web浏览器或程序片浏览器来进行。在写作这本书时，这看起来像留下了一个未定的争议。当我在WEB浏览器中运行这个程序时，printdemo（打印样本）窗口正好出现，但它却根本不能从浏览器中打印。</p>

  <p>13.17.3 剪贴板</p>

  <p>Java 1.1对系统剪贴板提供有限的操作支持（在Java.awt.datatransfer package里）。我们可以将字符串作这文字对象复制到剪贴板中，并且我们可以从剪贴板中粘贴文字到字符中对角中。当然，剪贴板被设计来容纳各种类型的数据，存在于剪贴板上的数据通过程序运行剪切和粘贴进入到程序中。虽然剪切板目前只支持字符串数据，Java的剪切板API通过“特色”概念提供了良好的可扩展性。当数据从剪贴板中出来时，它拥有一个相关的特色集，这个特色集可以被修改（例如，一个图形可以被表示成一些字符串或者一幅图像）并且我们会注意到如果特殊的剪贴板数据支持这种特色，我们会对此十分的感兴趣。</p>

  <p>下面的程序简单地对TextArea中的字符串数据进行剪切，复制，粘贴的操作做了示范。我们将注意到的是我们需要按照剪切、复制和粘贴的顺序进行工作。但如果我们看见一些其它程序中的TextField或者TextArea，我们会发现它们同样也自动地支持剪贴板的操作顺序。程序中简单地增加了剪贴板的程序化控制，如果我们想用它来捕捉剪贴板上的文字到一些非文字组件中就可以使用这种技术。</p>

  <p>//: CutAndPaste.java</p>

  <p>// Using the clipboard from Java 1.1</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.awt.datatransfer.*;</p>

  <p>public class CutAndPaste extends Frame {</p>

  <p>MenuBar mb = new MenuBar();</p>

  <p>Menu edit = new Menu("Edit");</p>

  <p>MenuItem</p>

  <p>cut = new MenuItem("Cut"),</p>

  <p>copy = new MenuItem("Copy"),</p>

  <p>paste = new MenuItem("Paste");</p>

  <p>TextArea text = new TextArea(20,20);</p>

  <p>Clipboard clipbd =</p>

  <p>getToolkit().getSystemClipboard();</p>

  <p>public CutAndPaste() {</p>

  <p>cut.addActionListener(new CutL());</p>

  <p>copy.addActionListener(new CopyL());</p>

  <p>paste.addActionListener(new PasteL());</p>

  <p>edit.add(cut);</p>

  <p>edit.add(copy);</p>

  <p>edit.add(paste);</p>

  <p>mb.add(edit);</p>

  <p>setMenuBar(mb);</p>

  <p>add(text, BorderLayout.CENTER);</p>

  <p>}</p>

  <p>class CopyL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>String selection = text.getSelectedText();</p>

  <p>StringSelection clipString =</p>

  <p>new StringSelection(selection);</p>

  <p>clipbd.setContents(clipString, clipString);</p>

  <p>}</p>

  <p>}</p>

  <p>class CutL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>String selection = text.getSelectedText();</p>

  <p>StringSelection clipString =</p>

  <p>new StringSelection(selection);</p>

  <p>clipbd.setContents(clipString, clipString);</p>

  <p>text.replaceRange("",</p>

  <p>text.getSelectionStart(),</p>

  <p>text.getSelectionEnd());</p>

  <p>}</p>

  <p>}</p>

  <p>class PasteL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>Transferable clipData =</p>

  <p>clipbd.getContents(CutAndPaste.this);</p>

  <p>try {</p>

  <p>String clipString =</p>

  <p>(String)clipData.</p>

  <p>getTransferData(</p>

  <p>DataFlavor.stringFlavor);</p>

  <p>text.replaceRange(clipString,</p>

  <p>text.getSelectionStart(),</p>

  <p>text.getSelectionEnd());</p>

  <p>} catch(Exception ex) {</p>

  <p>System.out.println("not String flavor");</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>CutAndPaste cp = new CutAndPaste();</p>

  <p>cp.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>cp.setSize(300,200);</p>

  <p>cp.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>创建和增加菜单及TextArea到如今似乎已变成一种单调的活动。这与通过工具组件创建的剪贴板字段clipbd有很大的区别。</p>

  <p>所有的动作都安置在接收器中。CopyL和Cupl接收器同样除了最后的CutL线以外删除被复制的线。特殊的两条线是StringSelection对象从字符串从创建并调用StringSelection的setContents()方法。说得更准确些，就是放一个字符串到剪切板上。</p>

  <p>在PasteL中，数据被剪贴板利用getContents()进行分解。任何返回的对象都是可移动的匿名的，并且我们并不真正地知道它里面包含了什么。有一种发现的方法是调用getTransferDateFlavors()，返回一个DataFlavor对象数组，表明特殊对象支持这种特点。我们同样能要求它通过我们感兴趣的特点直接地使用IsDataFlavorSupported()。但是在这里使用一种大胆的方法：调用getTransferData（ ）方法，假设里面的内容支持字符串特色，并且它不是个被分类在异常处理器中的难题 。</p>

  <p>在将来，我们希望更多的数据特色能够被支持。</p>

  <p>13.18 可视编程和Beans</p>

  <p>迄今为止，我们已看到Java对创建可重复使用的代码片工作而言是多么的有价值。“最大限度地可重复使用”的代码单元拥有类，因为它包含一个紧密结合在一起的单元特性（字段）和单元动作（方法），它们可以直接经过混合或通过继承被重复使用。</p>

  <p>继承和多形态性是面向对象编程的精华，但在大多数情况下当我们创建一个应用程序时，我们真正最想要的恰恰是我们最需要的组件。我们希望在我们的设计中设置这些部件就像电子工程师在电路板上创造集成电路块一样（在使用Java的情况下，就是放到WEB页面上）。这似乎会成为加快这种“模块集合”编制程序方法的发展。</p>

  <p>“可视化编程”最早的成功――非常的成功――要归功于微软公司的Visual Basic（VB，可视化Basic语言），接下来的第二代是Borland公司Delphi（一种客户/服务器数据库应用程序开发工具，也是Java Beans设计的主要灵感）。这些编程工具的组件的像征就是可视化，这是不容置疑的，因为它们通常展示一些类型的可视化组件，例如：一个按惯或一个TextField。事实上，可视化通常表现为组件可以非常精确地访问运行中程序。因此可视化编程方法的一部分包含从一个调色盘从拖放一个组件并将它放置到我们的窗体中。应用程序创建工具像我们所做的一样编写程序代码，该代码将导致正在运行的程序中的组件被创建。</p>

  <p>简单地拖放组件到一个窗体中通常不足以构成一个完整的程序。一般情况下，我们需要改变组件的特性，例如组件的色彩，组件的文字，组件连结的数据库，等等。特性可以参照属性在编程时进行修改。我们可以在应用程序构建工具中巧妙处置我们组件的属性，并且当我们创建程序时，构建数据被保存下来，所以当该程序被启动时，数据能被重新恢复。</p>

  <p>到如今，我们可能习惯于使用对象的多个特性，这也是一个动作集合。在设计时，可视化组件的动作可由事件部分地代表，意味着“任何事件都可以发生在组件上”。通常，由我们决定想发生的事件，当一个事件发生时，对所发生的事件连接代码。</p>

  <p>这是关键性的部分：应用程序构建工具可以动态地询问组件（利用映象）以发现组件支持的事件和属件。一旦它知道它们的状态，应用程序构建工具就可以显示组件的属性并允许我们修改它们的属性（当我们构建程序时，保存它们的状态），并且也显示这些事件。一般而言，我们做一些事件像双击一个事件以及应用程序构建工具创建一个代码并连接到事件上。当事件发生时，我们不得不编写执行代码。应用程序构建工具累计为我们做了大量的工作。结果我们可以注意到程序看起来像它所假定的那样运行，并且依赖应用程序构建工具去为我们管理连接的详细资料。可视化的编程工具如此成功的原因是它们明显加快构建的应用程序的处理过程――当然，用户接口作为应用程序的一部分同样的好。</p>

  <p>13.18.1 什么是Bean</p>

  <p>在经细节处理后，一个组件在类中被独特的具体化，真正地成为一块代码。关键的争议在于应用程序构建工具发现组件的属性和事件能力。为了创建一个VB组件，程序开发者不得不编写正确的同时也是复杂烦琐的代码片，接下来由某些协议去展现它们的事件和属性。Delphi是第二代的可视化编程工具并且这种开发语言主动地围绕可视化编程来设计因此它更容易去创建一个可视化组件。但是，Java带来了可视化的创作组件做为Java Beans最高级的“装备”，因为一个Bean就是一个类。我们不必再为制造任何的Bean而编写一些特殊的代码或者使用特殊的编程语言。事实上，我们唯一需要做的是略微地修改我们对我们方法命名的办法。方法名通知应用程序构建工具是否是一个属性，一个事件或是一个普通的方法。</p>

  <p>在Java的文件中，命名规则被错误地曲解为“设计范式”。这十分的不幸，因为设计范式（参见第16章）惹来不少的麻烦。命名规则不是设计范式，它是相当的简单：</p>

  <p>(1) 因为属性被命名为xxx，我们代表性的创建两个方法：getXxx()和setXxx()。注意get或set后的第一个字母小写以产生属性名。“get”和“set”方法产生同样类型的自变量。“set”和“get”的属性名和类型名之间没有关系。</p>

  <p>(2) 对于布尔逻辑型属性，我们可以使用上面的“get”和“set”方法，但我们也可以用“is”代替“ get”。</p>

  <p>(3) Bean的普通方法不适合上面的命名规则，但它们是公用的。</p>

  <p>4.对于事件，我们使用“listener（接收器）”方法。这种方法完全同我们看到过的方法相同：(addFooBarListener(FooBarListener)和removeFooBarListener(FooBarListener)方法用来处理FooBar事件。大多数时候内建的事件和接收器会满足我们的需要，但我们可以创建自己的事件和接收器接口。</p>

  <p>上面的第一点回答了一个关于我们可能注意到的从Java 1.0到Java 1.1的改变的问题：一些方法的名字太过于短小，显然改写名字毫无意义。现在我们可以看到为了制造Bean中的特殊的组件，大多数的这些修改不得不适合于“get”和“set”命名规则。</p>

  <p>现在，我们已经可以利用上面的这些指导方针去创建一个简单的Bean：</p>

  <p>//: Frog.java</p>

  <p>// A trivial Java Bean</p>

  <p>package frogbean;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>class Spots {}</p>

  <p>public class Frog {</p>

  <p>private int jumps;</p>

  <p>private Color color;</p>

  <p>private Spots spots;</p>

  <p>private boolean jmpr;</p>

  <p>public int getJumps() { return jumps; }</p>

  <p>public void setJumps(int newJumps) {</p>

  <p>jumps = newJumps;</p>

  <p>}</p>

  <p>public Color getColor() { return color; }</p>

  <p>public void setColor(Color newColor) {</p>

  <p>color = newColor;</p>

  <p>}</p>

  <p>public Spots getSpots() { return spots; }</p>

  <p>public void setSpots(Spots newSpots) {</p>

  <p>spots = newSpots;</p>

  <p>}</p>

  <p>public boolean isJumper() { return jmpr; }</p>

  <p>public void setJumper(boolean j) { jmpr = j; }</p>

  <p>public void addActionListener(</p>

  <p>ActionListener l) {</p>

  <p>//...</p>

  <p>}</p>

  <p>public void removeActionListener(</p>

  <p>ActionListener l) {</p>

  <p>// ...</p>

  <p>}</p>

  <p>public void addKeyListener(KeyListener l) {</p>

  <p>// ...</p>

  <p>}</p>

  <p>public void removeKeyListener(KeyListener l) {</p>

  <p>// ...</p>

  <p>}</p>

  <p>// An "ordinary" public method:</p>

  <p>public void croak() {</p>

  <p>System.out.println("Ribbet!");</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>首先，我们可看到Bean就是一个类。通常，所有我们的字段会被作为专用，并且可以接近的唯一办法是通过方法。紧接着的是命名规则，属性是jump，color，jumper，spots（注意这些修改是在第一个字母在属性名的情况下进行的）。虽然内部确定的名字同最早的三个例子的属性名一样，在jumper中我们可以看到属性名不会强迫我们使用任何特殊的内部可变的名字（或者，真的拥有一些内部的可变的属性名）。</p>

  <p>Bean事件的句柄是ActionEvent和KeyEvent，这是根据有关接收器的“add”和“remove”命名方法得出的。最后我们可以注意到普通的方法croak()一直是Bean的一部分，仅仅是因为它是一个公共的方法，而不是因为它符合一些命名规则。</p>

  <p>13.18.2 用Introspector提取BeanInfo</p>

  <p>当我们拖放一个Bean的调色板并将它放入到窗体中时，一个Bean的最关键的部分的规则发生了。应用程序构建工具必须可以创建Bean（如果它是默认的构建器的话，它就可以做）然后，在此范围外访问Bean的源代码，提取所有的必要的信息以创立属性表和事件处理器。</p>

  <p>解决方案的一部分在11章结尾部分已经显现出来：Java 1.1版的映象允许一个匿名类的所有方法被发现。这完美地解决了Bean的难题而无需我们使用一些特殊的语言关键字像在其它的可视化编程语言中所需要的那样。事实上，一个主要的原因是映象增加到Java 1.1版中以支持Beans（尽管映象同样支持对象串联和远程方法调用）。因为我们可能希望应用程序构建工具的开发者将不得不映象每个Bean并且通过它们的方法搜索以找到Bean的属性和事件。</p>

  <p>这当然是可能的，但是Java的研制者们希望为每个使用它的用户提供一个标准的接口，而不仅仅是使Bean更为简单易用，不过他们也同样提供了一个创建更复杂的Bean的标准方法。这个接口就是Introspector类，在这个类中最重要的方法静态的getBeanInfo()。我们通过一个类处理这个方法并且getBeanInfo()方法全面地对类进行查询，返回一个我们可以进行详细研究以发现其属性、方法和事件的BeanInfo对象。</p>

  <p>通常我们不会留意这样的一些事物――我们可能会使用我们大多数的现成的Bean，并且我们不需要了解所有的在底层运行的技术细节。我们会简单地拖放我们的Bean到我们窗体中，然后配置它们的属性并且为事件编写处理器。无论如何它都是一个有趣的并且是有教育意义的使用Introspector来显示关于Bean信息的练习，好啦，闲话少说，这里有一个工具请运行它（我们可以在forgbean子目录中找到它）：</p>

  <p>//: BeanDumper.java</p>

  <p>// A method to introspect a Bean</p>

  <p>import java.beans.*;</p>

  <p>import java.lang.reflect.*;</p>

  <p>public class BeanDumper {</p>

  <p>public static void dump(Class bean){</p>

  <p>BeanInfo bi = null;</p>

  <p>try {</p>

  <p>bi = Introspector.getBeanInfo(</p>

  <p>bean, java.lang.Object.class);</p>

  <p>} catch(IntrospectionException ex) {</p>

  <p>System.out.println("Couldn't introspect " +</p>

  <p>bean.getName());</p>

  <p>System.exit(1);</p>

  <p>}</p>

  <p>PropertyDescriptor[] properties =</p>

  <p>bi.getPropertyDescriptors();</p>

  <p>for(int i = 0; i &lt; properties.length; i++) {</p>

  <p>Class p = properties[i].getPropertyType();</p>

  <p>System.out.println(</p>

  <p>"Property type:\n " + p.getName());</p>

  <p>System.out.println(</p>

  <p>"Property name:\n " +</p>

  <p>properties[i].getName());</p>

  <p>Method readMethod =</p>

  <p>properties[i].getReadMethod();</p>

  <p>if(readMethod != null)</p>

  <p>System.out.println(</p>

  <p>"Read method:\n " +</p>

  <p>readMethod.toString());</p>

  <p>Method writeMethod =</p>

  <p>properties[i].getWriteMethod();</p>

  <p>if(writeMethod != null)</p>

  <p>System.out.println(</p>

  <p>"Write method:\n " +</p>

  <p>writeMethod.toString());</p>

  <p>System.out.println("====================");</p>

  <p>}</p>

  <p>System.out.println("Public methods:");</p>

  <p>MethodDescriptor[] methods =</p>

  <p>bi.getMethodDescriptors();</p>

  <p>for(int i = 0; i &lt; methods.length; i++)</p>

  <p>System.out.println(</p>

  <p>methods[i].getMethod().toString());</p>

  <p>System.out.println("======================");</p>

  <p>System.out.println("Event support:");</p>

  <p>EventSetDescriptor[] events =</p>

  <p>bi.getEventSetDescriptors();</p>

  <p>for(int i = 0; i &lt; events.length; i++) {</p>

  <p>System.out.println("Listener type:\n " +</p>

  <p>events[i].getListenerType().getName());</p>

  <p>Method[] lm =</p>

  <p>events[i].getListenerMethods();</p>

  <p>for(int j = 0; j &lt; lm.length; j++)</p>

  <p>System.out.println(</p>

  <p>"Listener method:\n " +</p>

  <p>lm[j].getName());</p>

  <p>MethodDescriptor[] lmd =</p>

  <p>events[i].getListenerMethodDescriptors();</p>

  <p>for(int j = 0; j &lt; lmd.length; j++)</p>

  <p>System.out.println(</p>

  <p>"Method descriptor:\n " +</p>

  <p>lmd[j].getMethod().toString());</p>

  <p>Method addListener =</p>

  <p>events[i].getAddListenerMethod();</p>

  <p>System.out.println(</p>

  <p>"Add Listener Method:\n " +</p>

  <p>addListener.toString());</p>

  <p>Method removeListener =</p>

  <p>events[i].getRemoveListenerMethod();</p>

  <p>System.out.println(</p>

  <p>"Remove Listener Method:\n " +</p>

  <p>removeListener.toString());</p>

  <p>System.out.println("====================");</p>

  <p>}</p>

  <p>}</p>

  <p>// Dump the class of your choice:</p>

  <p>public static void main(String[] args) {</p>

  <p>if(args.length &lt; 1) {</p>

  <p>System.err.println("usage: \n" +</p>

  <p>"BeanDumper fully.qualified.class");</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>Class c = null;</p>

  <p>try {</p>

  <p>c = Class.forName(args[0]);</p>

  <p>} catch(ClassNotFoundException ex) {</p>

  <p>System.err.println(</p>

  <p>"Couldn't find " + args[0]);</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>dump(c);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>BeanDumper.dump()是一个可以做任何工作的方法。首先它试图创建一个BeanInfo对象，如果成功地调用BeanInfo的方法，就产生关于属性、方法和事件的信息。在Introspector.getBeanInfo()中，我们会注意到有一个另外的自变量。由它来通知Introspector访问继承体系的地点。在这种情况下，它在分析所有对象方法前停下，因为我们对看到那些并不感兴趣。</p>

  <p>因为属性，getPropertyDescriptors()返回一组的属性描述符号。对于每个描述符号我们可以调用getPropertyType()方法彻底的通过属性方法发现类的对象。这时，我们可以用getName()方法得到每个属性的假名（从方法名中提取），getname()方法用getReadMethod()和getWriteMethod()完成读和写的操作。最后的两个方法返回一个可以真正地用来调用在对象上调用相应的方法方法对象（这是映象的一部分）。对于公共方法（包括属性方法），getMethodDescriptors( ）返回一组方法描述字符。每一个我们都可以得到相当的方法对象并可以显示出它们的名字。</p>

  <p>对于事件而言，getEventSetDescriptors()返回一组事件描述字符。它们中的每一个都可以被查询以找出接收器的类，接收器类的方法以及增加和删除接收器的方法。BeanDumper程序打印出所有的这些信息。</p>

  <p>如果我们调用BeanDumper在Frog类中，就像这样：</p>

  <p>java BeanDumper frogbean.Frog</p>

  <p>它的输出结果如下（已删除这儿不需要的额外细节）：</p>

  <p>class name: Frog</p>

  <p>Property type:</p>

  <p>Color</p>

  <p>Property name:</p>

  <p>color</p>

  <p>Read method:</p>

  <p>public Color getColor()</p>

  <p>Write method:</p>

  <p>public void setColor(Color)</p>

  <p>====================</p>

  <p>Property type:</p>

  <p>Spots</p>

  <p>Property name:</p>

  <p>spots</p>

  <p>Read method:</p>

  <p>public Spots getSpots()</p>

  <p>Write method:</p>

  <p>public void setSpots(Spots)</p>

  <p>====================</p>

  <p>Property type:</p>

  <p>boolean</p>

  <p>Property name:</p>

  <p>jumper</p>

  <p>Read method:</p>

  <p>public boolean isJumper()</p>

  <p>Write method:</p>

  <p>public void setJumper(boolean)</p>

  <p>====================</p>

  <p>Property type:</p>

  <p>int</p>

  <p>Property name:</p>

  <p>jumps</p>

  <p>Read method:</p>

  <p>public int getJumps()</p>

  <p>Write method:</p>

  <p>public void setJumps(int)</p>

  <p>====================</p>

  <p>Public methods:</p>

  <p>public void setJumps(int)</p>

  <p>public void croak()</p>

  <p>public void removeActionListener(ActionListener)</p>

  <p>public void addActionListener(ActionListener)</p>

  <p>public int getJumps()</p>

  <p>public void setColor(Color)</p>

  <p>public void setSpots(Spots)</p>

  <p>public void setJumper(boolean)</p>

  <p>public boolean isJumper()</p>

  <p>public void addKeyListener(KeyListener)</p>

  <p>public Color getColor()</p>

  <p>public void removeKeyListener(KeyListener)</p>

  <p>public Spots getSpots()</p>

  <p>======================</p>

  <p>Event support:</p>

  <p>Listener type:</p>

  <p>KeyListener</p>

  <p>Listener method:</p>

  <p>keyTyped</p>

  <p>Listener method:</p>

  <p>keyPressed</p>

  <p>Listener method:</p>

  <p>keyReleased</p>

  <p>Method descriptor:</p>

  <p>public void keyTyped(KeyEvent)</p>

  <p>Method descriptor:</p>

  <p>public void keyPressed(KeyEvent)</p>

  <p>Method descriptor:</p>

  <p>public void keyReleased(KeyEvent)</p>

  <p>Add Listener Method:</p>

  <p>public void addKeyListener(KeyListener)</p>

  <p>Remove Listener Method:</p>

  <p>public void removeKeyListener(KeyListener)</p>

  <p>====================</p>

  <p>Listener type:</p>

  <p>ActionListener</p>

  <p>Listener method:</p>

  <p>actionPerformed</p>

  <p>Method descriptor:</p>

  <p>public void actionPerformed(ActionEvent)</p>

  <p>Add Listener Method:</p>

  <p>public void addActionListener(ActionListener)</p>

  <p>Remove Listener Method:</p>

  <p>public void removeActionListener(ActionListener)</p>

  <p>====================</p>

  <p>这个结果揭示出了Introspector在从我们的Bean产生一个BeanInfo对象时看到的大部分内容。我们可注意到属性的类型和它们的名字是相互独立的。请注意小写的属性名。（当属性名开头在一行中有超过不止的大写字母，这一次程序就不会被执行。）并且请记住我们在这里所见到的方法名（例如读和与方法）真正地从一个可以被用来在对象中调用相关方法的方法对象中产生。</p>

  <p>通用方法列表包含了不相关的事件或者属性，例如croak()。列表中所有的方法都是我们可以有计划的为Bean调用，并且应用程序构建工具可以选择列出所有的方法，当我们调用方法时，减轻我们的任务。</p>

  <p>最后，我们可以看到事件在接收器中完全地分析研究它的方法、增加和减少接收器的方法。基本上，一旦我们拥有BeanInfo，我们就可以找出对Bean来说任何重要的事物。我们同样可以为Bean调用方法，即使我们除了对象外没有任何其它的信息（此外，这也是映象的特点）。</p>

  <p>13.18.3 一个更复杂的Bean</p>

  <p>接下的程序例子稍微复杂一些，尽管这没有什么价值。这个程序是一张不论鼠标何时移动都围绕它画一个小圆的 弧５蔽颐前聪率蟊昙保谄聊恢醒胂允疽桓鲎帧奥ang!”，并且一个动作接收器被激活。画布。当按下鼠标键时，我们可以改变的属性是圆的大小，除此之外还有被显示文字的色彩，大小，内容。BangBean同样拥有它自己的addActionListener()和removeActionListener()方法，因此我们可以附上自己的当用户单击在BangBean上时会被激活的接收器。这样，我们将能够确认可支持的属性和事件：</p>

  <p>//: BangBean.java</p>

  <p>// A graphical Bean</p>

  <p>package bangbean;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.io.*;</p>

  <p>import java.util.*;</p>

  <p>public class BangBean extends Canvas</p>

  <p>implements Serializable {</p>

  <p>protected int xm, ym;</p>

  <p>protected int cSize = 20; // Circle size</p>

  <p>protected String text = "Bang!";</p>

  <p>protected int fontSize = 48;</p>

  <p>protected Color tColor = Color.red;</p>

  <p>protected ActionListener actionListener;</p>

  <p>public BangBean() {</p>

  <p>addMouseListener(new ML());</p>

  <p>addMouseMotionListener(new MML());</p>

  <p>}</p>

  <p>public int getCircleSize() { return cSize; }</p>

  <p>public void setCircleSize(int newSize) {</p>

  <p>cSize = newSize;</p>

  <p>}</p>

  <p>public String getBangText() { return text; }</p>

  <p>public void setBangText(String newText) {</p>

  <p>text = newText;</p>

  <p>}</p>

  <p>public int getFontSize() { return fontSize; }</p>

  <p>public void setFontSize(int newSize) {</p>

  <p>fontSize = newSize;</p>

  <p>}</p>

  <p>public Color getTextColor() { return tColor; }</p>

  <p>public void setTextColor(Color newColor) {</p>

  <p>tColor = newColor;</p>

  <p>}</p>

  <p>public void paint(Graphics g) {</p>

  <p>g.setColor(Color.black);</p>

  <p>g.drawOval(xm - cSize/2, ym - cSize/2,</p>

  <p>cSize, cSize);</p>

  <p>}</p>

  <p>// This is a unicast listener, which is</p>

  <p>// the simplest form of listener management:</p>

  <p>public void addActionListener (</p>

  <p>ActionListener l)</p>

  <p>throws TooManyListenersException {</p>

  <p>if(actionListener != null)</p>

  <p>throw new TooManyListenersException();</p>

  <p>actionListener = l;</p>

  <p>}</p>

  <p>public void removeActionListener(</p>

  <p>ActionListener l) {</p>

  <p>actionListener = null;</p>

  <p>}</p>

  <p>class ML extends MouseAdapter {</p>

  <p>public void mousePressed(MouseEvent e) {</p>

  <p>Graphics g = getGraphics();</p>

  <p>g.setColor(tColor);</p>

  <p>g.setFont(</p>

  <p>new Font(</p>

  <p>"TimesRoman", Font.BOLD, fontSize));</p>

  <p>int width =</p>

  <p>g.getFontMetrics().stringWidth(text);</p>

  <p>g.drawString(text,</p>

  <p>(getSize().width - width) /2,</p>

  <p>getSize().height/2);</p>

  <p>g.dispose();</p>

  <p>// Call the listener's method:</p>

  <p>if(actionListener != null)</p>

  <p>actionListener.actionPerformed(</p>

  <p>new ActionEvent(BangBean.this,</p>

  <p>ActionEvent.ACTION_PERFORMED, null));</p>

  <p>}</p>

  <p>}</p>

  <p>class MML extends MouseMotionAdapter {</p>

  <p>public void mouseMoved(MouseEvent e) {</p>

  <p>xm = e.getX();</p>

  <p>ym = e.getY();</p>

  <p>repaint();</p>

  <p>}</p>

  <p>}</p>

  <p>public Dimension getPreferredSize() {</p>

  <p>return new Dimension(200, 200);</p>

  <p>}</p>

  <p>// Testing the BangBean:</p>

  <p>public static void main(String[] args) {</p>

  <p>BangBean bb = new BangBean();</p>

  <p>try {</p>

  <p>bb.addActionListener(new BBL());</p>

  <p>} catch(TooManyListenersException e) {}</p>

  <p>Frame aFrame = new Frame("BangBean Test");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(bb, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(300,300);</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>// During testing, send action information</p>

  <p>// to the console:</p>

  <p>static class BBL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>System.out.println("BangBean action");</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>最重要的是我们会注意到BangBean执行了这种串联化的接口。这意味着应用程序构建工具可以在程序设计者调整完属性值后利用串联为BangBean贮藏所有的信息。当Bean作为运行的应用程序的一部分被创建时，那些被贮藏的属性被重新恢复，因此我们可以正确地得到我们的设计。</p>

  <p>我们能看到通常同Bean一起运行的所有的字段都是专用的――允许只能通过方法来访问，通常利用“属性”结构。</p>

  <p>当我们注视着addActionListener()的签名时，我们会注意到它可以产生出一个TooManyListenerException（太多接收器异常）。这个异常指明它是一个单一的类型的，意味着当事件发生时，它只能通知一个接收器。一般情况下，我们会使用具有多种类型的事件，以便一个事件通知多个的接收器。但是，那样会陷入直到下一章我们才能准备好的结局中，因此这些内容会被重新回顾（下一个标题是“Java Beans 的重新回顾”）。单一类型的事件回避了这个难题。</p>

  <p>当我们按下鼠标键时，文字被安入BangBean中间，并且如果动作接收器字段存在，它的actionPerformed()方法就被调用，创建一个新的ActionEvent对象在处理过程中。无论何时鼠标移动，它的新座标将被捕捉，并且画布会被重画（像我们所看到的抹去一些画布上的文字）。</p>

  <p>main()方法增加了允许我们从命令行中测试程序的功能。当一个Bean在一个开发环境中，main()方法不会被使用，但拥有它是绝对有益的，因为它提供了快捷的测试能力。无论何时一个ActionEvent发生，main()方法都将创建了一个帧并安置了一个BangBean在它里面，还在BangBean中附上了一个简单的动作接收器以打印到控制台。当然，一般来说应用程序构建工具将创建大多数的Bean的代码。当我们通过BeanDumper或者安放BangBean到一个可激活Bean的开发环境中去运行BangBean时，我们会注意到会有很多额外的属性和动作明显超过了上面的代码。那是因为BangBean从画布中继承，并且画布就是一个Bean，因此我们看到它的属性和事件同样的合适。</p>

  <p>13.18.4 Bean的封装</p>

  <p>在我们可以安放一个Bean到一个可激活Bean的可视化构建工具中前，它必须被放入到标准的Bean容器里，也就是包含Bean类和一个表示“这是一个Bean”的清单文件的JAR（Java ARchive，Java文件）文件中。清单文件是一个简单的紧随事件结构的文本文件。对于BangBean而言，清单文件就像下面这样：</p>

  <p>Manifest-Version: 1.0</p>

  <p>Name: bangbean/BangBean.class</p>

  <p>Java-Bean: True</p>

  <p>其中，第一行指出清单文件结构的版本，这是SUN公司在很久以前公布的版本。第二行（空行忽略）对文件命名为BangBean.class。第三行表示“这个文件是一个Bean”。没有第三行，程序构建工具不会将类作为一个Bean来认可。</p>

  <p>唯一难以处理的部分是我们必须肯定“Name:”字段中的路径是正确的。如果我们回顾BangBean.java，我们会看到它在package bangbean（因为存放类路径的子目录称为“bangbean”）中，并且这个名字在清单文件中必须包括封装的信息。另外，我们必须安放清单文件在我们封装路径的根目录上，在这个例子中意味着安放文件在bangbean子目录中。这之后，我们必须从同一目录中调用Jar来作为清单文件，如下所示：</p>

  <p>jar cfm BangBean.jar BangBean.mf bangbean</p>

  <p>这个例子假定我们想产生一个名为BangBean.jar的文件并且我们将清单放到一个称为BangBean.mf文件中。</p>

  <p>我们可能会想“当我编译BangBean.java时，产生的其它类会怎么样呢？”哦，它们会在bangbean子目录中被中止，并且我们会注意到上面jar命令行的最后一个自变量就是bangbean子目录。当我们给jar子目录名时，它封装整个的子目录到jar文件中（在这个例子中，包括BangBean.java的源代码文件――对于我们自己的Bean我们可能不会去选择包含源代码文件。）另外，如果我们改变主意，解开打包的JAR文件，我们会发现我们清单文件并不在里面，但jar创建了它自己的清单文件（部分根据我们的文件），称为MAINFEST.MF并且安放它到META-INF子目录中（代表“meta-information”）。如果我们打开这个清单文件，我们同样会注意到jar为每个文件加入数字签名信息，其结构如下：</p>

  <p>Digest-Algorithms: SHA MD5</p>

  <p>SHA-Digest: pDpEAG9NaeCx8aFtqPI4udSX/O0=</p>

  <p>MD5-Digest: O4NcS1hE3Smnzlp2hj6qeg==</p>

  <p>一般来说，我们不必担心这些，如果我们要做一些修改，可以修改我们的原始的清单文件并且重新调用jar以为我们的Bean创建了一个新的JAR文件。我们同样也可以简单地通过增加其它的Bean的信息到我们清单文件来增加它们到JAR文件中。</p>

  <p>值得注意的是我们或许需要安放每个Bean到它自己的子目录中，因为当我们创建一个JAR文件时，分配JAR应用目录名并且JAR放置子目录中的任何文件到JAR文件中。我们可以看到Frog和BangBean都在它们自己的子目录中。</p>

  <p>一旦我们将我们的Bean正确地放入一个JAR文件中，我们就可以携带它到一个可以激活Bean的编程环境中使用。使用这种方法，我们可以从一种工具到另一种工具间交替变换，但SUN公司为Java Beans提供了免费高效的测试工具在它们的“Bean Development Kit，Bean开发工具”（BDK）称为“beanbox”。（我们可以从www.javasoft.com处下载。）在我们启动beanbox前，放置我们的Bean到beanbox中，复制JAR文件到BDK的“jars”子目录中。</p>

  <p>13.18.5 更复杂的Bean支持</p>

  <p>我们可以看到创建一个Bean显然多么的简单。在程序设计中我们几乎不受到任何的限制。Java Bean的设计提供了一个简单的输入点，这样可以提高到更复杂的层次上。这些高层次的问题超出了这本书所要讨论的范围，但它们会在此做简要的介绍。我们可以在http://java.sun.com/beans上找到更多的详细资料。</p>

  <p>我们增加更加复杂的程序和它的属性到一个位置。上面的例子显示一个独特的属性，当然它也可能代表一个数组的属性。这称为索引属性。我们简单地提供一个相应的方法（再者有一个方法名的命名规则）并且Introspector认可索引属性，因此我们的应用程序构建工具相应的处理。</p>

  <p>属性可以被捆绑，这意味着它们将通过PropertyChangeEvent通知其它的对象。其它的对象可以随后根据对Bean的改变选择修改它们自己。</p>

  <p>属性可以被束缚，这意味着其它的对象可以在一个属性的改变不能被接受时，拒绝它。其它的对象利用一个PropertyChangeEvent来通知，并且它们产生一个ProptertyVetoException去阻止修改的发生，并恢复为原来的值。</p>

  <p>我们同样能够改变我们的Bean在设计时的被描绘成的方法：</p>

  <p>(1) 我们可以为我们特殊的Bean提供一个定制的属性表。这个普通的属性表将被所有的Bean所使用，但当我们的Bean被选择时，它会自动地调用这张属性表。</p>

  <p>(2) 我们可以为一个特殊的属性创建一个定制的编辑器，因此普通的属性表被使用，但当我们指定的属性被调用时，编辑器会自动地被调用。</p>

  <p>(3)我们可以为我们的Bean提供一个定制的BeanInfo类，产生的信息不同于由Introspector默认产生的。</p>

  <p>(4) 它同样可能在所有的FeatureDescriptors中改变“expert”的开关模式，以辨别基本特征和更复杂的特征。</p>

  <p>13.18.6 Bean更多的知识</p>

  <p>另外有关的争议是Bean不能被编址。无论何时我们创建一个Bean，都希望它会在一个多线程的环境中运行。这意味着我们必须理解线程的出口，我们将在下一章中介绍。我们会发现有一段称为“Java Beans的回顾”的节会注意到这个问题和它的解决方案。</p>

  <p>13.19 Swing入门（注释⑦）</p>

  <p>通过这一章的学习，当我们的工作方法在AWT中发生了巨大的改变后（如果可以回忆起很久以前，当Java第一次面世时SUN公司曾声明Java是一种“稳定，牢固”的编程语言），可能一直有Java还不十分的成熟的感觉。的确，现在Java拥有一个不错的事件模型以及一个优秀的组件复用设计――JavaBeans。但GUI组件看起来还相当的原始，笨拙以及相当的抽象。</p>

  <p>⑦：写作本节时，Swing库显然已被Sun“固定”下来了，所以只要你下载并安装了Swing库，就应该能正确地编译和运行这里的代码，不会出现任何问题（应该能编译Sun配套提供的演示程序，以检测安装是否正确）。若遇到任何麻烦，请访问http://www.BruceEckel.com，了解最近的更新情况。</p>

  <p>而这就是Swing将要占领的领域。Swing库在Java 1.1之后面世，因此我们可以自然而然地假设它是Java 1.2的一部分。可是，它是设计为作为一个补充在Java 1.1版中工作的。这样，我们就不必为了享用好的UI组件库而等待我们的平台去支持Java 1.2版了。如果Swing库不是我们的用户的Java 1.1版所支持的一部分，并且产生一些意外，那他就可能真正的需要去下载Swing库了。</p>

  <p>Swing包含所有我们缺乏的组件，在整个本章余下的部分中：我们期望领会现代化的UI，来自按钮的任何事件包括到树状和网格结构中的图片。它是一个大库，但在某些方面它为任务被设计得相应的复杂――如果任何事都是简单的，我们不必编写更多的代码但同样设法运行我们的代码逐渐地变得更加的复杂。这意味着一个容易的入口，如果我们需要它我们得到它的强大力量。</p>

  <p>Swing相当的深奥，这一节不会去试图让读者理解，但会介绍它的能力和Swing简单地使我们着手使用库。请注意我们有意识的使用这一切变得简单。如果我们需要运行更多的，这时Swing能或许能给我们所想要的，如果我们愿意深入地研究，可以从SUN公司的在线文档中获取更多的资料。</p>

  <p>13.19.1 Swing有哪些优点</p>

  <p>当我们开始使用Swing库时，会注意到它在技术上向前迈出了巨大的一步。Swing组件是Bean，因此他们可以支持Bean的任何开发环境中使用。Swing提供了一个完全的UI组件集合。因为速度的关系，所有的组件都很小巧的（没有“重量级”组件被使用），Swing为了轻便在Java中整个被编写。</p>

  <p>最重要的是我们会希望Swing被称为“正交使用”；一旦我们采用了这种关于库的普遍的办法我们就可以在任何地方应用它们。这主要是因为Bean的命名规则，大多数的时候在我编写这些程序例子时我可以猜到方法名并且第一次就将它拼写正确而无需查找任何事物。这无疑是优秀库设计的品质证明。另外，我们可以广泛地插入组件到其它的组件中并且事件会正常地工作。</p>

  <p>键盘操作是自动被支持的――我们可以使用Swing应用程序而不需要鼠标，但我们不得不做一些额外的编程工作（老的AWT中需要一些可怕的代码以支持键盘操作）。滚动被毫不费力地支持――我们简单地将我们的组件到一个JScrollPane中，同样我们再增加它到我们的窗体中即可。其它的特征，例如工具提示条只需要一行单独的代码就可执行。</p>

  <p>Swing同样支持一些被称为“可插入外观和效果”的事物，这就是说UI的外观可以在不同的平台和不同的操作系统上被动态地改变以符合用户的期望。它甚至可以创造我们自己的外观和效果。</p>

  <p>13.19.2 方便的转换</p>

  <p>如果我们长期艰苦不懈地利用Java 1.1版构建我们的UI，我们并不需要扔掉它改变到Swing阵营中来。幸运的是，库被设计得允许容易地修改――在很多情况下我们可以简单地放一个“J”到我们老AWT组件的每个类名前面即可。下面这个例子拥有我们所熟悉的特色：</p>

  <p>//: JButtonDemo.java</p>

  <p>// Looks like Java 1.1 but with J's added</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>import javax.swing.*;</p>

  <p>public class JButtonDemo extends Applet {</p>

  <p>JButton</p>

  <p>b1 = new JButton("JButton 1"),</p>

  <p>b2 = new JButton("JButton 2");</p>

  <p>JTextField t = new JTextField(20);</p>

  <p>public void init() {</p>

  <p>ActionListener al = new ActionListener() {</p>

  <p>public void actionPerformed(ActionEvent e){</p>

  <p>String name =</p>

  <p>((JButton)e.getSource()).getText();</p>

  <p>t.setText(name + " Pressed");</p>

  <p>}</p>

  <p>};</p>

  <p>b1.addActionListener(al);</p>

  <p>add(b1);</p>

  <p>b2.addActionListener(al);</p>

  <p>add(b2);</p>

  <p>add(t);</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>JButtonDemo applet = new JButtonDemo();</p>

  <p>JFrame frame = new JFrame("TextAreaNew");</p>

  <p>frame.addWindowListener(new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e){</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>frame.getContentPane().add(</p>

  <p>applet, BorderLayout.CENTER);</p>

  <p>frame.setSize(300,100);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>frame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这是一个新的输入语句，但此外任何事物除了增加了一些“J”外，看起都像这Java 1.1版的AWT。同样，我们不恰当的用add()方法增加到Swing JFrame中，除此之外我们必须像上面看到的一样先准备一些“content pane”。我们可以容易地得到Swing一个简单的改变所带来的好处。</p>

  <p>因为程序中的封装语句，我们不得不调用像下面所写的一样调用这个程序：</p>

  <p>java c13.swing.JbuttonDemo</p>

  <p>在这一节里出现的所有的程序都将需要一个相同的窗体来运行它们。</p>

  <p>13.19.3 显示框架</p>

  <p>尽管程序片和应用程序都可以变得很重要，但如果在任何地方都使用它们就会变得混乱和毫无用处。这一节余下部分取代它们的是一个Swing程序例子的显示框架：</p>

  <p>//: Show.java</p>

  <p>// Tool for displaying Swing demos</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import javax.swing.*;</p>

  <p>public class Show {</p>

  <p>public static void</p>

  <p>inFrame(JPanel jp, int width, int height) {</p>

  <p>String title = jp.getClass().toString();</p>

  <p>// Remove the word "class":</p>

  <p>if(title.indexOf("class") != -1)</p>

  <p>title = title.substring(6);</p>

  <p>JFrame frame = new JFrame(title);</p>

  <p>frame.addWindowListener(new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e){</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>frame.getContentPane().add(</p>

  <p>jp, BorderLayout.CENTER);</p>

  <p>frame.setSize(width, height);</p>

  <p>frame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>那些想显示它们自己的类将从JPanel处继承并且随后为它们自己增加一些可视化的组件。最后，它们创建一个包含下面这一行程序的main()：</p>

  <p>Show.inFrame(new MyClass(), 500, 300);</p>

  <p>最后的两个自变量是显示的宽度和高度。</p>

  <p>注意JFrame的标题是用RTTI产生的。</p>

  <p>13.19.4 工具提示</p>

  <p>几乎所有我们利用来创建我们用户接口的来自于JComponent的类都包含一个称为setToolTipText(string）的方法。因此，几乎任何我们所需要表示的（对于一个对象jc来说就是一些来自JComponent的类）都可以安放在窗体中：</p>

  <p>jc.setToolTipText("My tip");</p>

  <p>并且当鼠标停在JComponent上一个超过预先设置的一个时间，一个包含我们的文字的小框就会从鼠标下弹出。</p>

  <p>13.19.5 边框</p>

  <p>JComponent同样包括一个称为setBorder()的方法，该方法允许我们安放一些各种各样有趣的边框到一些可见的组件上。下面的程序例子利用一个创建JPanel并安放边框到每个例子中的被称为showBorder()的方法，示</p>

  <div class="mbppagebreak"></div>
</body>
</html>
